#include"jni_utile.h"
#include<iostream>
#include <boost/asio/buffer.hpp>
#include <boost\asio.hpp>
#include <boost\locale.hpp>
#include <boost\scoped_array.hpp>
#include <exception>

jclass trans_cls = NULL;
jmethodID trans_handle = NULL;
extern std::string encode;
jclass timeout_cls = NULL;
jmethodID timeout_handle = NULL;

typedef boost::asio::buffers_iterator<boost::asio::streambuf::const_buffers_type> streambuf_iterator;

void copy_from_stream(JNIEnv* penv, jbyteArray src, boost::asio::streambuf& stream, size_t off, size_t len) {
	boost::asio::streambuf::const_buffers_type buf = stream.data();
	streambuf_iterator begin = streambuf_iterator::begin(buf);
	streambuf_iterator end = streambuf_iterator::end(buf);
	std::string str(begin, end);

	penv->SetByteArrayRegion(src, off, len, (jbyte*)str.c_str());
	stream.consume(len);
}

void call_trans_handle(JNIEnv *penv, jobject obj, ecode code, jint size) {
	if (trans_cls == NULL) {
		jclass cls = penv->FindClass("SerialPort/TranseFinish");

		if (cls != NULL) {
			trans_cls = (jclass)penv->NewGlobalRef(cls);
			penv->DeleteLocalRef(cls);
		}
	}

	if (trans_cls == NULL) {
		throw_serial_exception(penv, "Cannot find class 'SerialPort/TraseFinish'");
		return;
	}

	if (trans_handle == NULL) {
		trans_handle = penv->GetMethodID(trans_cls, "handle", "(ILjava/lang/String;I)V");
	}

	if (trans_handle == NULL) {
		throw_serial_exception(penv, "Cannot find method 'handle'");
		return;
	}

	std::string msg;
	if (code.value() == boost::system::errc::success) {
		msg = "";
	}
	else {
		msg = boost::locale::conv::to_utf<char>(code.message(), encode);
	}

	jstring str = penv->NewStringUTF(msg.c_str());

	penv->CallVoidMethod(obj, trans_handle, code.value(), str, size);

	penv->DeleteLocalRef(str);
}

void call_timeout_handle(JNIEnv *penv, jobject obj, const boost::system::error_code& error){

	if (timeout_cls == NULL) {
		jclass cls = penv->FindClass("SerialPort/TimeoutHandle");

		if (cls != NULL) {
			timeout_cls = (jclass)penv->NewGlobalRef(cls);
			penv->DeleteLocalRef(cls);
		}
	}

	if (timeout_cls == NULL) {
		throw_serial_exception(penv, "Cannot find class 'SerialPort/TimeoutHandle'");
		return;
	}

	if (timeout_handle == NULL) {
		timeout_handle = penv->GetMethodID(timeout_cls, "onTimeout", "(Ljava/lang/String;)V");
	}

	if (timeout_handle == NULL) {
		throw_serial_exception(penv, "Cannot find method 'onTimeout'");
		return;
	}

	jstring str;

	if (error.value() == boost::system::errc::success) {
		str = penv->NewStringUTF("");
	}
	else {
		std::string msg = boost::locale::conv::to_utf<char>(error.message(), encode);
		str = penv->NewStringUTF(msg.c_str());
	}

	penv->CallVoidMethod(obj, timeout_handle, str);
	
	penv->DeleteLocalRef(str);
}

jclass recv_cls = NULL;
jmethodID recv_handle = NULL;

void call_recv_handle(JNIEnv *penv, jobject obj, ecode code, const signed char* bytes, const size_t size) {
	//LOGD("here1.");
	if (recv_cls == NULL) {
		jclass cls = penv->FindClass("SerialPort/RecvFinish");

		if (cls != NULL) {
			recv_cls = (jclass)penv->NewGlobalRef(cls);
			penv->DeleteLocalRef(cls);
		}
	}

	if (recv_cls == NULL) {
		throw_serial_exception(penv, "Cannot find class 'SerialPort/RecvFinish'");
		return;
	}

	if (recv_handle == NULL) {
		recv_handle = penv->GetMethodID(recv_cls, "handle", "(ILjava/lang/String;[BI)V");
	}

	if (recv_handle == NULL) {
		throw_serial_exception(penv, "Cannot find method 'handle'");
		return;
	}

	std::string msg;
	if (code.value() == boost::system::errc::success) {
		msg = "";
	}
	else {
		try{
			msg = boost::locale::conv::to_utf<char>(code.message(), encode);
		}catch(std::exception& e){

			LOGD("%s",(const char*)e.what());
		}
	}

	jstring str = penv->NewStringUTF(msg.c_str());
	jbyteArray context;
	//LOGD("here7.");
	if (size <= 0) {
		context = penv->NewByteArray(size);
	}
	else {
		context = penv->NewByteArray(size);

		if (context != NULL) {
			penv->SetByteArrayRegion(context, 0, size, (const signed char*)bytes);
		}
		else {
			throw_serial_exception(penv, "cannot create 'jbyteArray'");
		}
	}

	penv->CallVoidMethod(obj, recv_handle, code.value(), str, context, -1);

	if (context != NULL) {
		penv->DeleteLocalRef(context);
	}

	penv->DeleteLocalRef(str);
}

void call_recv_handle(JNIEnv *penv, jobject obj, ecode code, const signed char* bytes, const size_t size,
	const size_t index_split) {
	if (recv_cls == NULL) {
		jclass cls = penv->FindClass("SerialPort/RecvFinish");

		if (cls != NULL) {
			recv_cls = (jclass)penv->NewGlobalRef(cls);
			penv->DeleteLocalRef(cls);
		}
	}
	
	if (recv_cls == NULL) {
		throw_serial_exception(penv, "Cannot find class 'SerialPort/RecvFinish'");
		return;
	}
	
	if (recv_handle == NULL) {
		recv_handle = penv->GetMethodID(recv_cls, "handle", "(ILjava/lang/String;[B)V");
	}
	
	if (recv_handle == NULL) {
		throw_serial_exception(penv, "Cannot find method 'handle'");
		return;
	}
	
	std::string msg;
	if (code.value() == boost::system::errc::success) {
		msg = "";
	}
	else {
		msg = boost::locale::conv::to_utf<char>(code.message(), encode);
	}
	
	jstring str = penv->NewStringUTF(msg.c_str());
	jbyteArray context;

	if (size <= 0) {
		context = penv->NewByteArray(size);
	}
	else {
		context = penv->NewByteArray(size);

		if (context != NULL) {
			penv->SetByteArrayRegion(context, 0, size, (const signed char*)bytes);
		}
		else {
			throw_serial_exception(penv, "cannot create 'jbyteArray'");
		}
	}

	penv->CallVoidMethod(obj, recv_handle, code.value(), str, context, index_split);

	if (context != NULL) {
		penv->DeleteLocalRef(context);
	}

	penv->DeleteLocalRef(str);
}

jclass g_cls_exception = NULL;

void throw_serial_exception(JNIEnv* penv, const char* msg) {

	if (g_cls_exception == NULL) {
		jclass cls = (penv)->FindClass("SerialPort/SerialException");

		if (cls != NULL) {
			g_cls_exception = (jclass)penv->NewGlobalRef(cls);
			penv->DeleteLocalRef(cls);
		}
	}
	/* if cls is NULL, an exception has already been thrown */

	if (g_cls_exception != NULL) {
		std::string msg_ch = boost::locale::conv::to_utf<char>(msg, encode);
		(penv)->ThrowNew(g_cls_exception, msg);

	}

	/* free the local ref */

	//(penv)->DeleteLocalRef(cls);
}

void warp_trans_handle(const boost::system::error_code& error, // Result of operation.
	std::size_t bytes_transferred, JavaVM* pjvm, jobject obj, boost::shared_array<jbyte>) {
	JNIEnv* jenv = NULL;
	
	jint ret = pjvm->AttachCurrentThread((void**)&jenv, NULL);

	if (ret == 0) {
		call_trans_handle(jenv, obj, error, bytes_transferred);
		jenv->DeleteGlobalRef(obj);
	}else {
		std::cout << "Get JNIEnv fail:" << ret << std::endl;
	}

}

void copy_from_stream(char* src, boost::asio::streambuf& stream, int len) {
	boost::asio::streambuf::const_buffers_type buf = stream.data();
	streambuf_iterator begin = streambuf_iterator::begin(buf);
	//streambuf_iterator end = streambuf_iterator::end(buf);

	std::copy(begin, (begin + len), src);
	//penv->SetByteArrayRegion(src, off, len, (jbyte*)str.c_str());
	stream.consume(len);
}

void warp_recv_handle_util(const boost::system::error_code& error, // Result of operation.
	std::size_t bytes_transferred, JavaVM* pjvm, jobject obj, SerialPortInfoPtr pserialInfo, Lock lock){
	JNIEnv* jenv = NULL;
	//std::cout << "time out2"<<std::endl;
	jint ret = pjvm->AttachCurrentThread((void**)&jenv, NULL);
	pserialInfo->cancel_async_wait();
	std::cout << "handle" << std::endl;
	if (ret == 0) {
		if(bytes_transferred > 0){
			boost::scoped_array<char> bytes(new char[bytes_transferred]);
			{
				copy_from_stream(bytes.get(), pserialInfo->get_instream(), bytes_transferred);
			}
		

			call_recv_handle(jenv, obj, error, (const signed char*)bytes.get(), bytes_transferred);
		}else{
			call_recv_handle(jenv, obj, error, NULL, bytes_transferred);
		}

		jenv->DeleteGlobalRef(obj);
	}
	else {
		std::cout << "Get JNIEnv fail:" << ret << std::endl;
	}
}

void warp_recv_handle(const boost::system::error_code& error, // Result of operation.
	std::size_t bytes_transferred, JavaVM* pjvm, jobject obj, SerialPortInfoPtr pserialInfo, Lock lock) {
	JNIEnv* jenv = NULL;

	jint ret = pjvm->AttachCurrentThread((void**)&jenv, NULL);
	pserialInfo->cancel_async_wait();
	std::cout << "handle" << std::endl;
	if (ret == 0) {

		if( bytes_transferred > 0 ){
			boost::scoped_array<char> bytes;
			{
				boost::asio::streambuf& stream = pserialInfo->get_instream();
				bytes_transferred = stream.size();
				bytes.reset(new char[bytes_transferred]);
				copy_from_stream(bytes.get(), stream, bytes_transferred);
			}

			call_recv_handle(jenv, obj, error, (const signed char*)bytes.get(), bytes_transferred);
		}else{
			call_recv_handle(jenv, obj, error, NULL, bytes_transferred);
		}

		jenv->DeleteGlobalRef(obj);
	}
	else {
		std::cout << "Get JNIEnv fail:" << ret << std::endl;
	}
}


void warp_timeout_handle(JavaVM* pjvm, jobject obj, const boost::system::error_code& error){

	JNIEnv* penv = NULL;

	jint ret = pjvm->AttachCurrentThread((void**)&penv, NULL);
	
	if (ret == 0) {
		call_timeout_handle(penv, obj, error);
		penv->DeleteGlobalRef(obj);
	}
	else {
		std::cout << "Get JNIEnv fail:" << ret << std::endl;
	}
	
}

int set_output_buffer(JNIEnv* penv, jobject out, boost::asio::streambuf& buf) {
	boost::asio::streambuf::const_buffers_type bufs = buf.data();
	jsize len = boost::asio::buffers_end(bufs) - boost::asio::buffers_begin(bufs);

	jbyteArray temp = penv->NewByteArray(len);
	int ret = 0;
	if (temp != NULL) {
		std::string line(boost::asio::buffers_begin(bufs), boost::asio::buffers_end(bufs));

		penv->SetByteArrayRegion(temp, 0, len, (const jbyte*)line.c_str());

		jclass cls = penv->GetObjectClass(out);

		if (cls != NULL) {
			jmethodID method = penv->GetMethodID(cls, "write", "([BII)V");

			if (NULL != method) {
				penv->CallVoidMethod(out, method, temp, 0, len);
			}
			else {
				ret = -3;
			}
			penv->DeleteLocalRef(cls);
		}
		else {
			ret = -2;
		}

		penv->DeleteLocalRef(temp);
	}
	else {
		ret = -1;
	}


	return ret;
}

/*void warp_recv_handle(const boost::system::error_code& error, // Result of operation.
	std::size_t bytes_transferred, JavaVM* pjvm, jobject obj,
	streambuf_ptr pstreambuf) {
	JNIEnv* jenv = NULL;

	jint ret = pjvm->AttachCurrentThread((void**)&jenv, NULL);

	if (ret == 0) {
		std::string msg;
		size_t len = 0;
		const char* bytes = NULL;
		std::string line;

		if (error.value() == boost::system::errc::success) {
			msg = "";
			boost::asio::streambuf::const_buffers_type bufs = pstreambuf->data();
			len = boost::asio::buffers_end(bufs) - boost::asio::buffers_begin(bufs);
			line.append(boost::asio::buffers_begin(bufs), boost::asio::buffers_end(bufs));
			bytes = line.c_str();
		}
		else {
			msg = boost::locale::conv::to_utf<char>(error.message(), encode);
		}

		call_recv_handle(jenv, obj, error, (const signed char*)bytes, len, bytes_transferred);

		jenv->DeleteGlobalRef(obj);
	}
	else {
		std::cout << "Get JNIEnv fail:" << ret << std::endl;
	}
}*/
